package io.kimmking.gateway.filters;

import com.alibaba.fastjson.JSON;
import io.kimmking.common.redis.repository.RedisRepository;
import io.kimmking.dubbo.common.unit.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import io.kimmking.gateway.config.WhiteListURLConfig;
import org.springframework.util.StringUtils;

import java.util.Objects;

/**
 * 所有不在白名单内的请求，其header必须带上token和userNo
 */
@Slf4j
@Component
public class TokenCheckGlobalFilter implements GlobalFilter, Ordered {

    private static final String TOKEN = "HEADER_TOKEN";

    /**
     * 此处请求头里userNo的值实际是username
     */
    private static final String USER_NO = "userNo";

    private static final RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();

    @Autowired
    private WhiteListURLConfig whiteListURLConfig;

    @Autowired
    private RedisRepository redisRepository;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        if(!whiteListURLConfig.getEnable())
            return chain.filter(exchange);

        //如果当前请求的url在白名单中，则直接通过
        if(isIgnore(exchange.getRequest().getURI().getPath()) ||
        isIgnore(exchange.getRequest().getURI().getRawPath()))
            return chain.filter(exchange);

        //开始验证token
        log.info("不在白名单，开始验证token");
        ServerHttpResponse response = exchange.getResponse();
        String token = exchange.getRequest().getHeaders().getFirst(TOKEN);
        if(StringUtils.isEmpty(token)) {
            log.error("lack of token in header");
            return setUnauthorizedResponse(exchange,"header 中缺少token!");
        }

        String userNo = exchange.getRequest().getHeaders().getFirst(USER_NO);
        /**
         * RedisTokenStore里存储的key为"uname_to_access:+clientid+用户名"的token值
         * 从redis中获取指定用户的accessToken与请求头里的token进行对比
         */
        String combineKey = "kmm"
                + "uname_to_access"
                + "yy"
                + Objects.requireNonNull(userNo, "lack of userNo in header");
        byte[] token_from_redis_bytes = redisRepository.getByByteKey(serializationStrategy.serialize(combineKey));
        OAuth2AccessToken oAuth2AccessToken = serializationStrategy.deserialize(token_from_redis_bytes, OAuth2AccessToken.class);
        String token_from_redis = oAuth2AccessToken.getValue();
        if(!StringUtils.isEmpty(token_from_redis)
                && token_from_redis.equals(token)){
            return chain.filter(exchange);
        }else {
            log.error("token from header is wrong or out of date");
            return setUnauthorizedResponse(exchange,"header中的token错误或者已过期!");
        }
    }

    private boolean isIgnore(String path) {
        return whiteListURLConfig.getIgnoreUrl()
                .stream()
                .map(url -> url.replace("/**", ""))
                .anyMatch(path::startsWith);
    }

    private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange, String msg) {
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);

        log.error("[鉴权异常处理-token令牌问题]请求路径:{}", exchange.getRequest().getPath());

        return response.writeWith(Mono.fromSupplier(() -> {
            DataBufferFactory bufferFactory = response.bufferFactory();
            return bufferFactory.wrap(JSON.toJSONBytes(ResponseResult.failed(msg)));
        }));
    }

    /**
     * 考虑到predicates过滤器的order是10150，所以需要排在该过滤器后面，这样请求的uri是最终的url了
     * @return
     */
    @Override
    public int getOrder() {
        return 10151;
    }
}
